#include "CBL_TinyXMLSerializer.h"

#include <tinyxml.h>
#include <string>
#include <sstream>
#include <wchar.h>

#include "CBL_Rtti.h"
#include "CBL_Debug.h"
#include "CBL_ClassFactory.h"

CB_NAMESPACE_START

CTinyXMLSerializer::CTinyXMLSerializer(EOutputType _Type, const IFileSystem& _rFileSystem, const char* _pFileName)
    : ISerializer(_Type) 
    , m_pCurrentNode(NULL)
{
    m_pDocument = new TiXmlDocument(_pFileName);
    
    if (IsReading())
    {
        m_FileHandle = _rFileSystem.OpenFile(_pFileName, IFileSystem::OpenRead);
        
        uint32 FileSize = m_FileHandle->GetFileSizeByte();
        char* pData = new char[FileSize];
        
        m_FileHandle->Read(pData, FileSize);
        m_pDocument->Parse(pData);
        
        delete pData;
        m_FileHandle.Release();
    }
    else
    {
        m_FileHandle = _rFileSystem.OpenFile(_pFileName, IFileSystem::OpenWrite | IFileSystem::OpenCreate | IFileSystem::OpenTruncate);
    }
}

// ------------------------------------------------------------------------------------

CTinyXMLSerializer::CTinyXMLSerializer(EOutputType _Type, const char* _pData)
    : ISerializer(_Type) 
    , m_pCurrentNode(NULL)
{
    m_pDocument = new TiXmlDocument();
    
    if (IsReading())
    {
        m_pDocument->Parse(_pData);
    }
}

// ------------------------------------------------------------------------------------

CTinyXMLSerializer::~CTinyXMLSerializer() 
{
    if (IsWriting())
    {
    	if (m_FileHandle.IsValid())
        {
            TiXmlPrinter Printer;
            
            m_pDocument->Accept(&Printer);
            m_FileHandle->Write(Printer.CStr(), Printer.Size());
        }
    }
    
    delete m_pDocument;
}

// ------------------------------------------------------------------------------------

Memory::CAutoBuffer<char> CTinyXMLSerializer::Flush()
{
	Memory::CAutoBuffer<char> Buffer;
    
    if (IsWriting())
    {
    	TiXmlPrinter Printer;
    	m_pDocument->Accept(&Printer);
    
    	if (m_FileHandle.IsValid())
        {
            m_FileHandle->Write(Printer.CStr(), Printer.Size());
            m_FileHandle.Release();
        }
        else
        {
        	uint32 BufferSize = Printer.Size();
        
         	char* pBuffer = new char[BufferSize];
        	memcpy(pBuffer, Printer.CStr(), BufferSize);
        
            Buffer.SetBuffer(pBuffer, BufferSize);
        }
    }
    
    return Buffer;
}

// ------------------------------------------------------------------------------------

TiXmlNode* CTinyXMLSerializer::GetCurrentNode()
{ 
    return (m_pCurrentNode) ? m_pCurrentNode : m_pDocument;
}

// ------------------------------------------------------------------------------------

const char* CTinyXMLSerializer::ReadElementValueString(const char* _pName, uint32 _Flags)
{
    const char* pValue = NULL;
    
    if ((_Flags & FlagInlineBlockData) == 0)
    {
        TiXmlElement* pElement = (_Flags & FlagCollectionItem) 
        	? GetCurrentNode()->NextSiblingElement(_pName)
        	: GetCurrentNode()->FirstChildElement(_pName);
        
        if (pElement == NULL)
        {
            Debug::Assert(CB_DEBUG_MSG((_Flags & FlagRequiredField) == 0, 
                                       "Node %s is flagged as required and was not found."), 
										_pName);
            
            return false; // ### return ###
        }
        
        pValue = pElement->Attribute("value");
    }
    else
    {
        TiXmlElement* pElement = GetCurrentNode()->ToElement();
        
        if (Debug::Verify(pElement != NULL, "Current node is no element node."))
            pValue = pElement->Attribute(_pName);
    }
    
    return pValue;
}


// ------------------------------------------------------------------------------------

bool CTinyXMLSerializer::Serialize(bool& _rValue, const char* _pName, uint32 _Flags) 
{
    if (IsReading())
    {
        const char* pValue = ReadElementValueString(_pName, _Flags);
        
        if (pValue == NULL)
        	_rValue = false;
        else
        	_rValue = (strncmp(pValue, "true", strlen(pValue)) == 0);
    }
    else
    {
        if ((_Flags & FlagInlineBlockData) == 0)
        {
            TiXmlElement NewNode(_pName);
            NewNode.SetAttribute("value", (_rValue) ? "true" : "false");
            
            GetCurrentNode()->InsertEndChild(NewNode);
        }
        else
        {
            TiXmlElement* pElement = GetCurrentNode()->ToElement();
            
            if (Debug::Verify(pElement != NULL, "Current node is no element node."))
                pElement->SetAttribute(_pName, (_rValue) ? "true" : "false");
        }
    }
    
    return true;
}

// ------------------------------------------------------------------------------------

bool CTinyXMLSerializer::Serialize(int8& _rValue, const char* _pName, uint32 _Flags) 
{
    if (IsReading())
    {
        const char* pValue = ReadElementValueString(_pName, _Flags);
        
        if (pValue == NULL)
        	_rValue = 0;
        else
        	_rValue = (int8)atoi(pValue);
    }
    else
    {
        if ((_Flags & FlagInlineBlockData) == 0)
        {
            TiXmlElement NewNode(_pName);
            NewNode.SetAttribute("value", _rValue);
            
            GetCurrentNode()->InsertEndChild(NewNode);
        }
        else
        {
            TiXmlElement* pElement = GetCurrentNode()->ToElement();
            
            if (Debug::Verify(pElement != NULL, "Current node is no element node."))
                pElement->SetAttribute(_pName, _rValue);
        }
    }
    
    return true;
}

// ------------------------------------------------------------------------------------

bool CTinyXMLSerializer::Serialize(int16& _rValue, const char* _pName, uint32 _Flags) 
{
    if (IsReading())
    {
        const char* pValue = ReadElementValueString(_pName, _Flags);
    
        if (pValue == NULL)
            _rValue = 0;
        else
            _rValue = (int16)atoi(pValue);
    }
    else
    {
        if ((_Flags & FlagInlineBlockData) == 0)
        {
            TiXmlElement NewNode(_pName);
            NewNode.SetAttribute("value", _rValue);
            
            GetCurrentNode()->InsertEndChild(NewNode);
        }
        else
        {
            TiXmlElement* pElement = GetCurrentNode()->ToElement();
            
            if (Debug::Verify(pElement != NULL, "Current node is no element node."))
                pElement->SetAttribute(_pName, _rValue);				
        }
    }
    
    return true;
}

// ------------------------------------------------------------------------------------

bool CTinyXMLSerializer::Serialize(int32& _rValue, const char* _pName, uint32 _Flags) 
{
    if (IsReading())
    {
        const char* pValue = ReadElementValueString(_pName, _Flags);
        
    	if (pValue == NULL)
        	_rValue = 0;
    	else
        	_rValue = (int32)atoi(pValue);
    }
    else
    {
        if ((_Flags & FlagInlineBlockData) == 0)
        {
            TiXmlElement NewNode(_pName);
            NewNode.SetAttribute("value", _rValue);
            
            GetCurrentNode()->InsertEndChild(NewNode);
        }
        else
        {
            TiXmlElement* pElement = GetCurrentNode()->ToElement();
            
            if (Debug::Verify(pElement != NULL, "Current node is no element node."))
                pElement->SetAttribute(_pName, _rValue);
        }
    }
    
    return true;
}

// ------------------------------------------------------------------------------------

bool CTinyXMLSerializer::Serialize(int64& _rValue, const char* _pName, uint32 _Flags)
{
    if (IsReading())
    {
        const char* pValue = ReadElementValueString(_pName, _Flags);
    
    	if (pValue == NULL)
        	_rValue = 0;
    	else
        	_rValue = CB_ATOI64(pValue);
    }
    else
    {
        if ((_Flags & FlagInlineBlockData) == 0)
        {
            TiXmlElement NewNode(_pName);
            NewNode.SetAttribute("value", (int)_rValue);
            
            GetCurrentNode()->InsertEndChild(NewNode);
        }
        else
        {
            TiXmlElement* pElement = GetCurrentNode()->ToElement();
            
            if (Debug::Verify(pElement != NULL, "Current node is no element node."))
                pElement->SetAttribute(_pName, (int)_rValue);
        }
    }
    
    return true;
}

// ------------------------------------------------------------------------------------

bool CTinyXMLSerializer::Serialize(uint8& _rValue, const char* _pName, uint32 _Flags) 
{
    if (IsReading())
    {
        const char* pValue = ReadElementValueString(_pName, _Flags);
        
        if (pValue == NULL)
            _rValue = 0;
        else
        	_rValue = (uint8)atoi(pValue);
    }
    else
    {
        if ((_Flags & FlagInlineBlockData) == 0)
        {
            TiXmlElement NewNode(_pName);
            NewNode.SetAttribute("value", _rValue);
            
            GetCurrentNode()->InsertEndChild(NewNode);
        }
        else
        {
            TiXmlElement* pElement = GetCurrentNode()->ToElement();
            
            if (Debug::Verify(pElement != NULL, "Current node is no element node."))
                pElement->SetAttribute(_pName, _rValue);
        }
    }
    
    return true;
}

// ------------------------------------------------------------------------------------

bool CTinyXMLSerializer::Serialize(uint16& _rValue, const char* _pName, uint32 _Flags) 
{
    if (IsReading())
    {
        const char* pValue = ReadElementValueString(_pName, _Flags);
        
        if (pValue == NULL)
            _rValue = 0;
        else
            _rValue = (uint16)atoi(pValue);
    }
    else
    {
        if ((_Flags & FlagInlineBlockData) == 0)
        {
            TiXmlElement NewNode(_pName);
            NewNode.SetAttribute("value", _rValue);
            
            GetCurrentNode()->InsertEndChild(NewNode);
        }
        else
        {
            TiXmlElement* pElement = GetCurrentNode()->ToElement();
            
            if (Debug::Verify(pElement != NULL, "Current node is no element node."))
                pElement->SetAttribute(_pName, _rValue);
        }
    }
    
    return true;
}

// ------------------------------------------------------------------------------------

bool CTinyXMLSerializer::Serialize(uint32& _rValue, const char* _pName, uint32 _Flags) 
{
    if (IsReading())
    {
        const char* pValue = ReadElementValueString(_pName, _Flags);
    
        if (pValue == NULL)
            _rValue = 0;
        else
            _rValue = (uint32)atoi(pValue);
    }
    else
    {
        if ((_Flags & FlagInlineBlockData) == 0)
        {
            TiXmlElement NewNode(_pName);
            NewNode.SetAttribute("value", _rValue);
            
            GetCurrentNode()->InsertEndChild(NewNode);
        }
        else
        {
            TiXmlElement* pElement = GetCurrentNode()->ToElement();
            
            if (Debug::Verify(pElement != NULL, "Current node is no element node."))
                pElement->SetAttribute(_pName, _rValue);
        }
    }
    
    return true;
}

// ------------------------------------------------------------------------------------

bool CTinyXMLSerializer::Serialize(uint64& _rValue, const char* _pName, uint32 _Flags) 
{
    if (IsReading())
    {
        const char* pValue = ReadElementValueString(_pName, _Flags);       
    
        if (pValue == NULL)
            _rValue = 0;
        else
            _rValue = CB_ATOI64(pValue);
    }
    else
    {
        if ((_Flags & FlagInlineBlockData) == 0)
        {
            TiXmlElement NewNode(_pName);
            NewNode.SetAttribute("value", (int)_rValue);
            
            GetCurrentNode()->InsertEndChild(NewNode);
        }
        else
        {
            TiXmlElement* pElement = GetCurrentNode()->ToElement();
            
            if (Debug::Verify(pElement != NULL, "Current node is no element node."))
                pElement->SetAttribute(_pName, (int)_rValue);
        }
    }
    
    return true;
}

// ------------------------------------------------------------------------------------

bool CTinyXMLSerializer::Serialize(uint32& _rValue, const char** _pTranslationStringArray, uint32 _ArrayLength, const char* _pName, uint32 _Flags)
{
    if (IsReading())
    {
        const char* pValue = ReadElementValueString(_pName, _Flags);
        
        for (uint32 i(0); i<_ArrayLength; ++i)
        {
            if (strcmp(_pTranslationStringArray[i], pValue) == 0)
            {
                _rValue = i;
                return true; // ### return ###
            }
        }
    
        _rValue = 0;
    }
    else
    {
        if ((_Flags & FlagInlineBlockData) == 0)
        {
            TiXmlElement NewNode(_pName);
            NewNode.SetAttribute("value", _pTranslationStringArray[_rValue]);
            
            GetCurrentNode()->InsertEndChild(NewNode);
        }
        else
        {
            TiXmlElement* pElement = GetCurrentNode()->ToElement();
            
            if (Debug::Verify(pElement != NULL, "Current node is no element node."))
                pElement->SetAttribute(_pName, _pTranslationStringArray[_rValue]);
        }
    }
    
    return true;
}

// ------------------------------------------------------------------------------------

bool CTinyXMLSerializer::Serialize(float& _rValue, const char* _pName, uint32 _Flags) 
{
    if (IsReading())
    {
        const char* pValue = ReadElementValueString(_pName, _Flags);
        
        if (pValue == NULL)
            _rValue = 0;
        else
            _rValue = (float)atof(pValue);
    }
    else
    {
        if ((_Flags & FlagInlineBlockData) == 0)
        {
            TiXmlElement NewNode(_pName);
            NewNode.SetDoubleAttribute("value", (double)_rValue);
            
            GetCurrentNode()->InsertEndChild(NewNode);
        }
        else
        {
            TiXmlElement* pElement = GetCurrentNode()->ToElement();
            
            if (Debug::Verify(pElement != NULL, "Current node is no element node."))
                pElement->SetDoubleAttribute(_pName, _rValue);
        }
    }
    
    return true;
}

// ------------------------------------------------------------------------------------

bool CTinyXMLSerializer::Serialize(double& _rValue, const char* _pName, uint32 _Flags) 
{
    if (IsReading())
    {
        const char* pValue = ReadElementValueString(_pName, _Flags);
    
        if (pValue == NULL)
            _rValue = 0;
        else
            _rValue = atof(pValue);
    }
    else
    {
        if ((_Flags & FlagInlineBlockData) == 0)
        {
            TiXmlElement NewNode(_pName);
            NewNode.SetDoubleAttribute("value", _rValue);
            
            GetCurrentNode()->InsertEndChild(NewNode);
        }
        else
        {
            TiXmlElement* pElement = GetCurrentNode()->ToElement();
            
            if (Debug::Verify(pElement != NULL, "Current node is no element node."))
                pElement->SetDoubleAttribute(_pName, _rValue);
        }
    }
    
    return true;
}

// ------------------------------------------------------------------------------------

bool CTinyXMLSerializer::Serialize(char* _pValue, size_t _BufferSize, const char* _pName, uint32 _Flags) 
{
    if (IsReading())
    {
        const char* pValue = ReadElementValueString(_pName, _Flags);
        
        if (pValue != NULL)
        	strncpy(_pValue, pValue, _BufferSize);
    }
    else
    {
        if ((_Flags & FlagInlineBlockData) == 0)
        {
            TiXmlElement NewNode(_pName);
            TiXmlText TextNode(_pValue);
            
            NewNode.InsertEndChild(TextNode);
            GetCurrentNode()->InsertEndChild(NewNode);
        }
        else
        {
            TiXmlElement* pElement = GetCurrentNode()->ToElement();
            
            if (Debug::Verify(pElement != NULL, "Current node is no element node."))
                pElement->SetAttribute(_pName, _pValue);
        }
    }
    
    return true;
}

// ------------------------------------------------------------------------------------

bool CTinyXMLSerializer::Serialize(wchar_t* CB_UNUSED(_pValue), size_t CB_UNUSED(_BufferSize), const char* CB_UNUSED(_pName), uint32 CB_UNUSED(_Flags)) 
{
     // TODO
    return false;
}

// ------------------------------------------------------------------------------------

bool CTinyXMLSerializer::Serialize(std::string& _rValue, const char* _pName, uint32 _Flags) 
{
    if (IsReading())
    {
        const char* pValue = ReadElementValueString(_pName, _Flags);
        
        if (pValue != NULL)
        	_rValue = pValue;
    }
    else
    {
        if ((_Flags & FlagInlineBlockData) == 0)
        {
            TiXmlElement NewNode(_pName);
            TiXmlText TextNode(_rValue.c_str());
            NewNode.InsertEndChild(TextNode);
        
            GetCurrentNode()->InsertEndChild(NewNode);
        }
        else
        {
            TiXmlElement* pElement = GetCurrentNode()->ToElement();
            
            if (Debug::Verify(pElement != NULL, "Current node is no element node."))
                pElement->SetAttribute(_pName, _rValue.c_str());
        }
    }
    
    return true;
}

// ------------------------------------------------------------------------------------

bool CTinyXMLSerializer::Serialize(std::wstring& CB_UNUSED(_rValue), const char* CB_UNUSED(_pName), uint32 CB_UNUSED(_Flags)) 
{
    // TODO	
    return false;
}

// ------------------------------------------------------------------------------------

bool CTinyXMLSerializer::Serialize(ISerializeable& _rObject, const char* _pName, uint32 _Flags) 
{
    TiXmlNode* pPreviousNode = GetCurrentNode();
    
    if (IsReading())
    {
        m_pCurrentNode = (_Flags & FlagCollectionItem) 
            ? GetCurrentNode()->NextSiblingElement(_pName)
            : GetCurrentNode()->FirstChildElement(_pName);
    
        if (m_pCurrentNode == NULL)
        {
            Debug::Assert(CB_DEBUG_MSG((_Flags & FlagRequiredField) == 0, 
                          "Node %s is flagged as required and was not found."), 
                          _pName);
            
            return false; // ### return ###
        }
        
        _rObject.Serialize(*this);
    }
    else
    {
        TiXmlElement NewNode(_pName);
        NewNode.SetAttribute("class", RTTI::GetClassName(_rObject));
        
        m_pCurrentNode = GetCurrentNode()->InsertEndChild(NewNode);
        _rObject.Serialize(*this);
    }
    
    m_pCurrentNode = pPreviousNode;
    return true;
}

// ------------------------------------------------------------------------------------

bool CTinyXMLSerializer::Serialize(ISerializeable** _ppObject, const char* _pName, uint32 _Flags) 
{
    TiXmlNode* pPreviousNode = GetCurrentNode();
    
    if (IsReading())
    {
        m_pCurrentNode = (_Flags & FlagCollectionItem) 
            ? GetCurrentNode()->NextSiblingElement(_pName)
            : GetCurrentNode()->FirstChildElement(_pName);
        
        if (m_pCurrentNode == NULL)
        {
            Debug::Assert(CB_DEBUG_MSG((_Flags & FlagRequiredField) == 0, 
                          "Node %s is flagged as required and was not found."), 
                          _pName);
            
            return false; // ### return ###
        }
        
        if (*_ppObject == NULL)
        {
            TiXmlElement* pElement = m_pCurrentNode->ToElement();
            *_ppObject = static_cast<ISerializeable*>(CClassFactory::GetInstance().NewObject(pElement->Attribute("class")));
        }
        
        (*_ppObject)->Serialize(*this);
    }
    else
    {
        TiXmlElement NewNode(_pName);
        NewNode.SetAttribute("class", RTTI::GetClassName(*_ppObject));
        
        m_pCurrentNode = GetCurrentNode()->InsertEndChild(NewNode);
        (*_ppObject)->Serialize(*this);
    }
    
    m_pCurrentNode = pPreviousNode;
    return true;
}

// ------------------------------------------------------------------------------------

bool CTinyXMLSerializer::SerializeBlockStart(const char* _pName, uint32 _Flags)
{
    if (IsReading())
    {
        m_pCurrentNode = (_Flags & FlagCollectionItem) 
            ? GetCurrentNode()->NextSiblingElement(_pName)
            : GetCurrentNode()->FirstChildElement(_pName);
        
        if (m_pCurrentNode == NULL)
        {
            Debug::Assert(CB_DEBUG_MSG((_Flags & FlagRequiredField) == 0, 
                          "Node %s is flagged as required and was not found."), 
                          _pName);
            
            return false; // ### return ###
        }
    }
    else
    {
        TiXmlElement NewNode(_pName);
        m_pCurrentNode = GetCurrentNode()->InsertEndChild(NewNode);
    }
    
    return true;
}

// ------------------------------------------------------------------------------------

bool CTinyXMLSerializer::SerializeBlockEnd(const char* CB_UNUSED(_pName), uint32 CB_UNUSED(_Flags))
{
    m_pCurrentNode = GetCurrentNode()->Parent();
    return true;
}

// ------------------------------------------------------------------------------------

CB_NAMESPACE_END